#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Create: 2020-07-31 11:08:52
"""Python 脚本模板
1. 本段文本必须置于文件的开头，`__author__ = '749B'`也算一行代码，要放在后面
2. 开头要加一段兼容python2的字符编码的语句，并且python3中也不会报错，因为有if
3. 设置BASE_DIR为脚本文件的当前目录的绝对路径
4. 使用logging记录日志到文件
5. 写入到文件的函数，尽量使用logging来记录
6. 装饰器，记录函数的运行时间
7. 主函数，里面有一些零碎的技巧
8. 捕获主函数的异常，记录到日志。注意：如果主函数的装饰器有异常是捕获不到的。
"""
__author__ = '749B'

# 下面这段兼容python2，python3中也不会报错，因为有if
import sys
DEFAULT_ENCODING = 'utf-8'
if sys.getdefaultencoding() != DEFAULT_ENCODING:
    # pylint: disable = undefined-variable
    reload(sys)
    # pylint: disable = no-member
    sys.setdefaultencoding(DEFAULT_ENCODING)

# 绝对路径
import os.path
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# 日志，适合测试环境，生产可以用下面的文件轮询日志
import logging
logging.basicConfig(
    level=logging.INFO,
    # 放开下面的filename，就是写入到文件
    #filename=os.path.join(BASE_DIR, "reports/example.log"),
    # 可以是 `%(levelname)10.3s` 这样写
    # 前面的数字表示字符串宽度，实现右对齐
    # 后面的数字是截断的字符数，只取前3个
    format="%(asctime)s %(levelname).3s %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    )

# 文件轮询的日志，适合生产环境
import logging.handlers
def set_rotating_file_handler(**kwargs):
    """设置文件轮询的日志
    参考 logging.basicConfig() 函数编写

    关键字参数:
    filename    写入的文件名。必须
    datefmt     日期格式
    format      日志格式
    level       记录的日志等级
    """
    # 假设只需要1个handler，如果已经有了就直接返回
    assert not logging.Logger.root.handlers, "不能重复添加handler，直接返回"
    filename = kwargs.pop("filename", None)
    assert filename, "必须指定关键字参数filename，直接返回"
    dfs = kwargs.pop("datefmt", None)
    fs = kwargs.pop("format", "%(asctime)s %(levelname).3s %(message)s")
    # 根据文件大小轮询
    # backupCount=5，保留个备份，一个会有6个文件：test.log,test{1..5}.log
    # maxBytes=1024*1024*10，这个大小为10M
    hdlr = logging.handlers.RotatingFileHandler(
        filename=filename, encoding='utf-8',
        maxBytes=3000000, backupCount=5
        )
    fmt = logging.Formatter(fmt=fs, datefmt=dfs)
    hdlr.setFormatter(fmt)
    logging.Logger.root.addHandler(hdlr)
    level = kwargs.pop("level", None)
    if level is not None:
        logging.Logger.root.setLevel(level)
    if kwargs:
        keys = ', '.join(kwargs.keys())
        raise ValueError('Unrecognised argument(s): %s' % keys)

# 这个函数只可以调用执行一次，并且也不能执行过 logging.basicConfig()
set_rotating_file_handler(
    level=logging.INFO,  # 这里不要写死告警等级，引用一个全局变量来控制
    filename=os.path.join(BASE_DIR, "%s.log" % os.path.basename(os.path.splitext(__file__)[0])),
    format="%(asctime)s %(levelname).3s 第%(lineno)d行: %(message)s",  # 可以不设置，函数本身有默认设置
    datefmt="%Y-%m-%d %H:%M:%S",
    )

# 写入文件
def log_to_file(msg, path):
    """写入到文件(已弃用，改用logging记录到文件)
    msg: 写入的信息，可以是多行文本。也可以是列表，列表需要每个元素的结尾有\n
        ["第一行\n", "第二行\n", "第三行"] 最后一个元素没有加\n，因为函数最后都会写一个\n
    path: 相对路径，与全局变量 BASE_DIR 拼接为绝对路径
    """
    path = os.path.join(BASE_DIR, path)
    with open(path, 'a') as file:
        file.writelines(msg)
        file.write('\n')

# 装饰器，记录函数运行时间
import functools
import time
DEFAULT_FMT = '[{elapsed:.6f}s] {func_name}({arg_str}) -> {result}'
def run_time(func=None, fmt=DEFAULT_FMT):
    if not callable(func):
        if func is not None:
            fmt = func
        return functools.partial(run_time, fmt=fmt)
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        func_name = func.__name__
        arg_list = []
        if args:
            arg_list.append(', '.join(repr(arg) for arg in args))
        if kwargs:
            pairs = ['%s=%r' % (k, w) for k, w in sorted(kwargs.items())]
            arg_list.extend(pairs)
        arg_str = ', '.join(arg_list)
        print(fmt.format(**locals()))
        return result
    return wrapper

# 主函数
@run_time
def main():
    # 技巧一：这里的拆包，pylint会提示错误，加上下面的注释，就可以忽略。
    # 鼠标移上去看下括号里的内容: pylint(unbalanced-tuple-unpacking)
    # pylint: disable = unbalanced-tuple-unpacking
    arg1, arg2 = sys.argv[1:3]
    del arg1, arg2

    # 技巧二：textwrap来格式化文件。这里的多行字符串可以整齐的现在代码里，缩进会被去掉。
    import textwrap
    str1 = textwrap.dedent("""\
        {
        'key1': 'Value1',
        "key2": "Value2",
        'Array1': ["a1", "a2", "a3"],
        }
        """)
    print(str1)

    # 技巧三：字符串转Python数据类型，用ast模块来处理抽象的语法解析
    # 和 json 的比较：
    # json的规则比较严，最后一个元素不能加逗号。始终要小心处理最后一个元素的逗号
    # 不能用单引号，必须用双引号。如果传递来的参数本身有引号，就不能单双引号混用来避免冲突
    # 和 eval 的比较：
    # eval 也能做同样的事情，但是效果太强大，可以任意执行命令，重大安全隐患
    # literal_eval()函数会判断需要计算的内容计算后是不是合法的python类型，如果是则进行运算，否则就不进行运算
    import ast
    dic1 = ast.literal_eval(str1)
    print(dic1)

    logging.info("执行主函数 main()")
    
    
if __name__ == "__main__":
    import traceback
    try:
        main()
    except Exception as e:
        error_msg = traceback.format_exc()
        logging.error(error_msg)
